//Original Upload.cs by Jouni Heikniemi (http://www.heikniemi.net/hc/archives/000205.html).
// Changes:
//    - Edited namespace for ease of use
//    - Support for writing messages to a DebugOutput object
//    - Removed use of StringDictionary class, which
//      mangles the case of the keys it stores.
//      Instead, use a plain Hashtable.

// This class is part of JHLib (http://www.heikniemi.net/jhlib/),
// and the JHLib web page has this text:
//
//    JHLib is free. It is not released under any formal license such as GPL;
//    it's just plainly and simply free. You can do whatever you wish with the code [...]

using System;
using System.Collections;
using System.IO;
using System.Net;

namespace Marmoset
{
    /// <summary>
    /// Provides functionality for programmatically uploading files 
    /// with the HTTP protocol.
    /// </summary>
    public class HttpUpload
    {

        // Prevent construction
        private HttpUpload() { }

        /// <summary>
        /// Uploads the given file to the given url.
        /// </summary>
        /// <param name="pathname">The pathname of the file to be uploaded.</param>
        /// <param name="uri">The URI to which the file shall be sent.</param>
        /// <param name="fieldName">The name of the form field for the upload.</param>
        /// <param name="cookies">Cookies to be sent with the request.</param>
        /// <param name="credentials">Login credentials to be passed.</param>
        public static HttpWebResponse UploadFile(string pathname, Uri uri, string fieldName,
                                                 CookieContainer cookies, ICredentials credentials)
        {

            return Upload(uri, cookies, credentials, new UploadSpec(pathname, fieldName));
        }


        /// <summary>
        /// Uploads the given byte array to the given url.
        /// </summary>
        /// <param name="data">The data to be uploaded.</param>
        /// <param name="fileName">The name to be sent as the filename.</param>
        /// <param name="uri">The URI to which the file shall be sent.</param>
        /// <param name="fieldName">The name of the form field for the upload.</param>
        /// <param name="cookies">Cookies to be sent with the request.</param>
        /// <param name="credentials">Login credentials to be passed.</param>
        public static HttpWebResponse UploadByteArray(byte[] data, string fileName, Uri uri, string fieldName,
                                                      CookieContainer cookies, ICredentials credentials)
        {

            return Upload(uri, cookies, credentials, new UploadSpec(data, fileName, fieldName));
        }

        /// <summary>
        /// Uploads the given data.
        /// </summary>
        /// <param name="uri">The URI to which the data shall be sent.</param>
        /// <param name="cookies">Cookies to be sent with the request.</param>
        /// <param name="credentials">Login credentials to be passed.</param>
        /// <param name="objects">The data to be sent.</param>
        public static HttpWebResponse Upload(Uri uri, CookieContainer cookies,
          ICredentials credentials, params UploadSpec[] objects)
        {

            return Upload(uri, null, cookies, credentials, null, objects);
        }

        /// <summary>
        /// Uploads the given data.
        /// </summary>
        /// <param name="uri">The URI to which the data shall be sent.</param>
        /// <param name="formFields">Form fields to be posted along with the file.</param>
        /// <param name="cookies">Cookies to be sent with the request.</param>
        /// <param name="credentials">Login credentials to be passed.</param>
        /// <param name="objects">The data to be sent.</param>
        public static HttpWebResponse Upload(
            Uri uri,
            Hashtable formFields,
            CookieContainer cookies,
            ICredentials credentials,
            DebugOutput debugOut,
            params UploadSpec[] objects)
        {
            if (debugOut == null)
            {
                debugOut = new NoopDebugOutput();
            }

            // Initialize the request object
            HttpWebRequest req = (WebRequest.Create(uri) as HttpWebRequest);
            if (cookies != null) req.CookieContainer = cookies;
            if (credentials != null) req.Credentials = credentials;

            string boundary = Guid.NewGuid().ToString().Replace("-", "");
            req.ContentType = "multipart/form-data; boundary=" + boundary;
            req.Method = "POST";

            MemoryStream postData = new MemoryStream();
            string newLine = "\r\n";
            StreamWriter sw = new StreamWriter(postData);

            if (formFields != null)
                foreach (DictionaryEntry de in formFields)
                {
                    //debugOut.WriteToOutput("  ==> sending form field " + de.Key + "=" + de.Value);
                    sw.Write("--" + boundary + newLine);
                    sw.Write("Content-Disposition: form-data; name=\"{0}\"{1}{1}{2}{1}",
                      de.Key,
                      newLine,
                      de.Value
                    );
                }

            foreach (UploadSpec us in objects)
            {
                sw.Write("--" + boundary + newLine);
                sw.Write(
                  "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"{2}",
                  us.FieldName,
                  us.FileName,
                  newLine
                );
                sw.Write("Content-Type: application/octet-stream" + newLine + newLine);
                sw.Flush();

                postData.Write(us.Contents, 0, us.Contents.Length);
                sw.Write(newLine);
            }

            sw.Write("--{0}--{1}", boundary, newLine);
            sw.Flush();

            req.ContentLength = postData.Length;
            using (Stream s = req.GetRequestStream())
                postData.WriteTo(s);
            postData.Close();

            return (req.GetResponse() as HttpWebResponse);
        }
    }


    /// <summary>
    /// Holds the information about the file(s) to be uploaded.
    /// </summary>
    public struct UploadSpec
    {

        private byte[] contents;
        private string fileName;
        private string fieldName;

        /// <summary>
        /// The byte array content to be uploaded.
        /// </summary>
        public byte[] Contents
        {
            get { return contents; }
            set { contents = value; }
        }

        /// <summary>
        /// The name of the file to be uploaded.
        /// </summary>
        public string FileName
        {
            get { return fileName; }
            set { fileName = value; }
        }

        /// <summary>
        /// The HTML form field the file should be uploaded into.
        /// </summary>
        public string FieldName
        {
            get { return fieldName; }
            set { fieldName = value; }
        }

        /// <summary>
        /// Creates a new upload spec based on a byte array.
        /// </summary>
        /// <param name="contents">The contents to be uploaded.</param>
        /// <param name="fileName">The file to be uploaded.</param>
        /// <param name="fieldName">The field name as which this file shall be sent to.</param>
        public UploadSpec(byte[] contents, string fileName, string fieldName)
        {
            this.contents = contents;
            this.fileName = fileName;
            this.fieldName = fieldName;
        }


        /// <summary>
        /// Creates a new upload spec based on a file name.
        /// </summary>
        /// <param name="pathname"></param>
        /// <param name="fieldName"></param>
        public UploadSpec(string pathname, string fieldName)
        {
            using (FileStream inFile = new FileStream(pathname, FileMode.Open))
            {
                byte[] inBytes = new byte[inFile.Length];
                inFile.Read(inBytes, 0, inBytes.Length);
                this.contents = inBytes;
            }
            this.fileName = Path.GetFileName(pathname);
            this.fieldName = fieldName;
        }
    }

}
